require 'rexml/document'
require 'builder'

class String
    def is_plural?
      self == self.pluralize
    end

    def is_singular?
      self == self.singularize    
    end
end

module DynamicXML

  module RubyKeywords
    @@ruby_keywords = [  "alias",
                         "and",
                         "begin",
                         "break",
                         "case",
                         "catch",
                         "class",
                         "def",
                         "do",
                         "elsif",
                         "else",
                         "fail",
                         "ensure",
                         "for",
                         "end",
                         "if",
                         "in",
                         "module",
                         "next",
                         "not",
                         "or",
                         "raise",
                         "redo",
                         "rescue",
                         "retry",
                         "return",
                         "then",
                         "throw",
                         "super",
                         "unless",
                         "undef",
                         "until",
                         "when",
                         "while",
                         "yield",
                         "BEGIN",
                         "END" ]
                         
      def get_ruby_keywords
          @@ruby_keywords
      end

  end

  module XMLExample
    @@xml_menu = <<-END
      <?xml version="1.0" encoding="UTF-8"?>
      <Menu xmlns:lang="http://www.volantis.com/xmlns/2005/10/storefront/language">
        <title>Discovery Mobile</title>
        <subtitle>Welcome to Discovery!</subtitle>
        <body-text/>
        <menu-items>
          <menu-item>95.0 /data/shared-content/menus/discovery-top-ten.Menu</menu-item>
        </menu-items>
        <menu-items>
          <menu-item>94.0 /data/shared-content/menus/carousel-items.Menu</menu-item>
        </menu-items>
        <menu-items>
          <menu-item>95.18 /data/shared-content/menus/articles.Menu</menu-item>
        </menu-items>
        <menu-items>
          <menu-item>568.0 /data/shared-content/menus/ringtones.Menu</menu-item>
        </menu-items>
        <menu-items>
          <menu-item>650.0 /data/shared-content/menus/discovery-tv.Menu</menu-item>
        </menu-items>
        <footer-text/>
        <footer-links>447.0 /data/shared-content/links/homepage.FooterLinks</footer-links>
        <start-date>2006-06-06T17:00:13.25+00:00</start-date>
        <end-date>2006-06-06T17:00:13.249+00:00</end-date>
        <carousel-start-date>2006-06-06T14:38:24.248+00:00</carousel-start-date>
        <carousel-step>1</carousel-step>
        <carousel-window/>
        <carousel-interval>P1Y0M0DT0H0M0.0S</carousel-interval>
        <banner>586.0 /data/shared-content/banners/discovery-tv.Banner</banner>
        <fallback-item>585.23 /data/shared-content/fallbacks/default.Fallback</fallback-item>
        <override-fallback-banner>false</override-fallback-banner>
        <override-fallback-footer-text>false</override-fallback-footer-text>
        <override-fallback-footer-links>true</override-fallback-footer-links>
    </Menu>
    END

    @@xml_menu_with_rkeywords = <<-END    
    <?xml version="1.0" encoding="UTF-8"?>
      <Menu xmlns:lang="http://www.volantis.com/xmlns/2005/10/storefront/language">
        <title>RingTones</title>
        <description>Have fun with the cool ringtones on your phone !</description>
        <icon/>
        <menu-sections>
        <title>Dynamic ringtones</title>
        <style>standard</style>
        <dynamic-search>
        <title>Superb ringtones</title>
        <description>Dynamic ring tones menu</description>
        <category/>
        <class>Ringtone</class>
        </dynamic-search>
        </menu-sections>
        <start-date>2006-09-29T11:28:58.834+01:00</start-date>
        <end-date>2006-09-29T11:28:58.835+01:00</end-date>
        <banner/>
        <charge-code>default</charge-code>
      </Menu>
    END

    @@xml_image = <<-END
      <?xml version="1.0" encoding="UTF-8"?>
      <Image xmlns:lang='http://www.volantis.com/xmlns/2005/10/storefront/language'>
        <charge-code>Default charge code</charge-code>
        <preview-variants>
          <href>583.0 /asset/chimpgene_goto.jpg</href>
          <mimetype>image/jpeg</mimetype>
          <height>175</height>
          <width>175</width>
          <aspect-ratio>1.0</aspect-ratio>
          <bit-depth/>
          <codec/>
          <resize/>
          <alt-text/>
          <charge-code/>
          <transcodable/>
          <rights-owner/>
        </preview-variants>
        <title>Chimpgene</title>
        <subtitle/>
        <start-date>2006-08-11T14:42:59.148+00:00</start-date>
        <end-date>2006-08-11T14:42:59.148+00:00</end-date>
        <thumbnail>528.0 /asset/lightning_138x44.jpg</thumbnail>
      </Image>
    END

  end

  class XMLObject
      include RubyKeywords  

      @@sf_lang_ns = 'http://www.volantis.com/xmlns/2005/10/storefront/language'

      def initialize xml = nil
        @orig_xml = xml
      end

      def set_orig_xml xml
          @orig_xml = xml
      end

      def get_orig_xml
          @orig_xml
      end

      def to_xml
          xml = Builder::XmlMarkup.new(:indent => 4, :margin => 0)
          xml.instruct!
          tag_name = get_sf_class_name
          xml.tag!(tag_name, "xmlns:lang" => @@sf_lang_ns) {
              add_xml xml, self
          }
          xml.target!
      end

      def get_sf_class_name
          if self.class.to_s.include?('::')
              self.class.to_s[self.class.to_s.rindex('::')+2...self.class.to_s.length]
          else
              self.class.to_s
          end
      end

     def set_accessor(acc_name)
        eval("def self.#{acc_name}; @#{acc_name}; end;")
        eval("def self.#{acc_name}=(#{acc_name}) ; @#{acc_name}=#{acc_name}; end;")
     end

    private
      attr_accessor :orig_xml

    def add_xml xml_builder_obj, element
        element.singleton_methods.each do |var|
            unless var.include?('=')
                var_decoded = decode_name(var)
                if element.send(var).kind_of? Array
#                    name = var.pluralize.gsub("_","-")
                    name = var.gsub("_","-")                    
                    element.send(var).each { |elem|
                        xml_builder_obj.tag!(name) {
                            add_xml xml_builder_obj, elem
                        }
                    }
                else
                    xml_builder_obj.tag!(var_decoded.gsub("_","-"), element.send(var))
                end
            end
        end
    end

public
    #Parses the item reference field +ref_field+ and returns the hash with +:asset_id+ and +:path+
    def parse_references_field ref_field
        index = ref_field.index(" ")
        if index.nil?
            #{:asset_id => "", :path => array[0].to_s}
            if ref_field =~ /[d]+\.[d]+/
                return { :asset_id => ref_field, :path => "" }
            else
                return { :asset_id => "", :path => ref_field }
            end
        else
            return {:asset_id => ref_field[0...index], :path => ref_field[index+1...ref_field.length] }
        end
    end

    def get_id_from_ref ref_field
        parse_references_field(ref_field)[:asset_id]
    end

    def get_path_from_ref ref_field
        parse_references_field(ref_field)[:path]
    end

    # method for encoding accessor's name incase it was ruby keyword
    def encode_name name
        get_ruby_keywords.include?(name) ? name = 'xml_' + name : name
    end

    # method for decoding accessor's name if it was ruby keyword
    def decode_name name
        name.starts_with?('xml_') ? name =  name[4...name.length] : name
    end

  end

private
  class XMLObjectFactory
      include XMLExample

      def create_object classname
          unless eval("defined? DynamicXML::#{classname}") && Class === eval("DynamicXML::#{classname}")
            eval("class DynamicXML::#{classname} < DynamicXML::XMLObject; end;")
          end
          eval("DynamicXML::#{classname}.new")
      end

     def set_doc xml_document
         doc = REXML::Document.new xml_document
         doc.root
     end

     def parse_xml rexml_element
#        puts rexml_element.name.humanize
        new_object = create_object(rexml_element.name.gsub("-","_").singularize.camelize)
        new_object.set_orig_xml rexml_element.to_s
        rexml_element.elements.each do |elem|
            var_name = new_object.encode_name(elem.name.gsub("-","_"))
            if elem.has_elements?
                unless new_object.respond_to?("#{var_name}".intern)
                    new_object.instance_variable_set("@#{var_name}".intern, [])
                    set_accessors(new_object, "#{var_name}")
                end
                eval("new_object.#{var_name}.push(parse_xml(elem))")
            else
                new_object.instance_variable_set("@#{var_name}".intern, elem.text)
                set_accessors(new_object, "#{var_name}")
            end
        end
        new_object
     end

     def self.create_object xml
        factory = self.new
        factory.parse_xml factory.set_doc(xml)
     end

################ Tests ##############################
     def self.run_test
        self.create_object @@xml_image
     end

     def self.run_test_another
        self.create_object @@xml_menu
     end


     def self.run_test_with_rkeyw
        self.create_object @@xml_menu_with_rkeywords
     end

     def self.run_test_another_arr
        arr = []
        arr << self.create_object(@@xml_image)
        arr << self.create_object(@@xml_image)
        arr
     end

     def self.test
         self.create_object "Aaa"
     end
#####################################################

     private
     def set_accessors(obj, acc_name)
        eval("def obj.#{acc_name}; @#{acc_name}; end;")
        eval("def obj.#{acc_name}=(#{acc_name}) ; @#{acc_name}=#{acc_name}; end;")
     end

  end

end
